import logging
import socket
import struct
import time
import traceback

from PyQt5.QtCore import QThread, pyqtSignal

import errorhandle
from tools.rebarparser import RebarParser, parse_rebar_data, HEADER, SINGLE_PACK_SIZE
from tools.tools import basic_log_config, load_yaml


class RebarConnector(QThread):
    trigger = pyqtSignal(list)
    triggerExcept = pyqtSignal(int)

    def __init__(self, rebarConfig):
        super().__init__()
        self.rebar = []
        self.rebar_socket = None
        self.rebarConfig = rebarConfig
        self.isConnected = False
        self.isRun = False
        self.buffer = b''

    def open_rebar(self):
        logging.info('Init rebar socket....')
        try:
            if self.rebar_socket:
                self.rebar_socket.close()
            self.rebar_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.rebar_socket.connect((self.rebarConfig['ADDR'], self.rebarConfig['PORT']))
            self.rebar_socket.setblocking(False)
            self.isConnected = True
        except Exception as e:
            traceback.print_exc()
            self.isConnected = False
            self.triggerExcept.emit(errorhandle.SERVER_CONNECT_EXCEPTION)
            return errorhandle.SERVER_CONNECT_EXCEPTION
        self.rebar_socket.settimeout(self.rebarConfig['TIMEOUT'])
        return 0

    def send(self, data):
        """
        Send data to radar, beware that the data must be converted to bytes, or exception occur!

        :param data: the bytes data which is supposed to send
        :return: 0 if success
                 errorhandle.SEND_INSTRUCT_ERROR if an exception when sending(返回的数字和发送byte长度不一，或者发送失败）
                 errorhandle.EMPTY_DATA_ERROR if an empty bytes is checked(所要发送的字节为空）
                 errorhandle.DISCONNECT_ERROR if the connexion is out(连接已经断了)
        """
        if data is None or len(data) == 0:
            logging.error("The data is empty...ERROR CODE: " + str(errorhandle.EMPTY_DATA_ERROR))
            return errorhandle.EMPTY_DATA_ERROR
        if self.isConnected:
            tryTimes = 0
            while True:
                try:
                    logging.info("Trying to send data.." + str(data))
                    res = self.rebar_socket.send(data)
                    logging.info("Data is sent: " + str(res))
                    if res != len(data):
                        logging.error("NET send data failure..Error Code:" + str(errorhandle.SEND_INSTRUCT_ERROR))
                        return errorhandle.SEND_INSTRUCT_ERROR
                    else:
                        return 0
                except Exception as e:
                    if tryTimes < 3:
                        time.sleep(0.1)
                        logging.error(e)
                        tryTimes += 1
                        continue
                    else:
                        self.isConnected = False
                        logging.error(str(e) + ', ERROR CODE:' + str(errorhandle.SEND_INSTRUCT_ERROR))
                        return errorhandle.SEND_INSTRUCT_ERROR
        else:
            logging.error("Socket is disconnected when trying to send data.." + str(errorhandle.DISCONNECT_ERROR))
            return errorhandle.DISCONNECT_ERROR

    def recv_tcp(self, recvSize):
        tryTimes = 0
        if self.isConnected:
            while True:
                try:
                    res = self.rebar_socket.recv(recvSize)
                    return res
                except Exception as e:
                    if tryTimes < 30:
                        logging.warning("Try to recv data from radar : " + str(tryTimes))
                        tryTimes += 1
                        time.sleep(0.1)
                        continue
                    else:
                        # self.connected = False
                        logging.error("NET receives Radar data failure.." + str(e))
                        return errorhandle.RECV_DATA_ERROR
        else:
            logging.error("Socket disconnect when trying to receive data..")
            return errorhandle.DISCONNECT_ERROR

    def recv(self, recvSize):
        """
        Receive data from radar with radar mode 测距轮

        :param recvSize: the bytes size
        :return: the byte data received from radar
                 -1 if the wheels is not moving, just ignore it
                 errorhandle.DISCONNECT_ERROR if the connexion is down
        """
        if self.isConnected:
            try:
                res = self.rebar_socket.recv(recvSize)
                return res
            except Exception as e:
                return -1
        else:
            logging.error("Socket disconnect when trying to send data..")
            return errorhandle.DISCONNECT_ERROR

    def reconnect(self):
        time.sleep(0.2)
        return self.open_rebar()

    def disconnect(self):
        """
        Disconnect to device, set isConnected to False
        :return:
        """

        self.rebar_socket.close()
        self.isConnected = False

    def compo_package(self, package):
        """
        有时候接到的数据是不完整的， 如果不完整，缺失的数据段会在之后发送过来，软件层面需要识别不完整数据并拼凑好

        :param package: 收到的数据包
        :return: 数据完整且正确/与之前的数据拼凑完成=》返回完整数据包
                 数据不完整/数据错误/无法拼凑 =》 返回-1
        """
        if package[0:2] == HEADER:
            self.buffer = b''  # clear buffer to next package
            return package
        else:
            header_index = find_header_index(package, HEADER)
            if len(self.buffer) > 0:  # buffer is not 0, last buffer is waiting to compo
                if header_index != -1:
                    pack_len = len(self.buffer) + header_index
                    if pack_len == SINGLE_PACK_SIZE:
                        res = self.buffer + package[0: header_index]
                        self.buffer = package[header_index:]
                        return res
                    else:
                        self.buffer = package[header_index:]
                        return -1
                else:
                    self.buffer = b''
                    return -1
            else:  # buffer is nothing, package is erupted, reject it
                self.buffer = package[header_index:]
                return -1

    def run(self):
        """
        Class is runnable, in runnable target function, if the device is connected and that is allowed to
        run, it receive data from socket and compose incomplete data frame, if current data is complete,
        parse it and emit parsed result to main thread, otherwise, emit an exception signal to main thread.
        :return:
        """
        if self.isConnected:
            while self.isRun:
                package = self.recv(SINGLE_PACK_SIZE)
                if type(package) != int:
                    package = self.compo_package(package)
                    if type(package) == int:
                        continue
                elif package == errorhandle.DISCONNECT_ERROR:
                    self.triggerExcept.emit(package)
                    self.stop()
                    break
                else:
                    continue
                parsedData = parse_rebar_data(package)
                if type(parsedData) == list:
                    self.trigger.emit(parsedData)
                if type(parsedData) == int:
                    logging.error("Rec or parse data exception!" + str(package))
                    # self.triggerExcept.emit(parsedData)
                    continue
                time.sleep(self.rebarConfig['FREQ'])

    def stop(self):
        self.isRun = False
        self.execute_cmd('stop')

    def execute_cmd(self, cmd):
        """
        Execute commands or settings to device.

        :param cmd: START/STOP/RANGE N/DIAMETER N/MEASURE/GUESS
        :return: result of sending data
        """
        dataHex = ''
        if cmd == 'start':
            start = self.rebarConfig['START']
            dataHex = struct.pack("%dB" % (len(start)), *start)
        if cmd == 'stop':
            stop = self.rebarConfig['STOP']
            dataHex = struct.pack("%dB" % (len(stop)), *stop)
        if cmd.split(' ')[0] == 'range':
            rangeCMD = [self.rebarConfig['RANGE'][int(cmd.split(' ')[1])]]
            dataHex = struct.pack("%dB" % (len(rangeCMD)), *rangeCMD)
        if cmd.split(' ')[0] == 'diameter':
            diamCMD = [int(cmd.split(' ')[1])]
            dataHex = struct.pack("%dB" % (len(diamCMD)), *diamCMD)
        if cmd == 'measure':
            diamCMD = self.rebarConfig['MEASURE']
            dataHex = struct.pack("%dB" % (len(diamCMD)), *diamCMD)
        if cmd == 'guess':
            diagCMD = self.rebarConfig['GUESS']
            dataHex = struct.pack("%dB" % (len(diagCMD)), *diagCMD)
        return self.send(dataHex)


def find_header_index(package, header):
    for index, ele in enumerate(package):
        if index+1 < len(package) and \
                ele == header[0] and package[index + 1] == header[1]:
            return index
    return -1


def testConnexion():
    basic_log_config()
    config = load_yaml('../configs/appconfig.yml')
    ip = config['REBAR_CONFIG']['ADDR']
    port = config['REBAR_CONFIG']['PORT']
    print(config['REBAR_CONFIG'])
    start = [0x35]
    dataHex = struct.pack("%dB" % (len(start)), *start)
    a = RebarConnector(config['REBAR_CONFIG'])
    b = RebarParser()
    a.open_rebar()
    a.send(dataHex)
    resData = []
    # for i in range(0, 30):
    #     if i > 25:
    #         resData.append(a.recv(1024))
    print("Recv data:...")
    for i in range(0, 20):
        raw = a.recv(1024)
        print(parse_rebar_data(raw))
    end = struct.pack("%dB" % (len(config['REBAR_CONFIG']['STOP'])), *config['REBAR_CONFIG']['STOP'])
    a.send(end)
    # for i in resData:
    #     print(i)
    # saveData(resData)
    # stop = [0x36]
    # dataHex = struct.pack("%dB" % (len(start)), *stop)
    # a.send(stop)
    print(a.recv_tcp(1024))
    a.disconnect()

# testConnexion()